package util

import database.DBConnection
import database.DB
import java.io.File
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMembers

/**
 * 根据实体类生成伴生对象代码
 */
inline fun <reified T> convertClassToObject() {
    val clazz = T::class
    val name = clazz.simpleName
    println("companion object : TableSchema(\"${humpToLine(name!!)}\") {")
    clazz.declaredMembers.filterIsInstance<KProperty<*>>().forEach {
        println("    val ${it.name} = column(\"${humpToLine(it.name)}\")")
    }
    println("}")
}

/**
 * mysql和pgsql数据库自动生成实体类
 * @receiver DBConnection 数据库查询类
 * @param dbName String 数据库名
 * @param path String 生成实体类的路径
 */
fun DBConnection.generateEntity(dbName: String, path: String) {
    val dir = File(path)
    if (!dir.exists()) {
        dir.mkdir()
    }

    val packageName = path.replace(Regex("src/\\w+/\\w+/"), "").replace("/", ".").trim { it == '.' }

    when (this.db) {
        DB.MYSQL -> generateEntityForMysql(this, dbName, path, packageName)
        DB.PGSQL -> generateEntityForPgsql(this, dbName, path, packageName)
        // TODO
        else -> throw TypeCastException("生成实体类功能暂不支持此类数据库")
    }
}

/**
 * mysql数据库自动生成实体类
 * @param dbConnection DBConnection 数据库查询类
 * @param dbName String 数据库名
 * @param path String 生成实体类的路径
 * @param packageName String 包名
 */
fun generateEntityForMysql(dbConnection: DBConnection, dbName: String, path: String, packageName: String) {
    val tables = dbConnection.nativeSelect("SHOW TABLES").queryMap()
    if (tables.isEmpty()) {
        return
    }

    val tableNames = tables.map {
        val key = it.keys.toList()[0]
        it[key].toString()
    }

    tableNames.forEach {
        val columnSql =
            "SELECT COLUMN_NAME, DATA_TYPE, EXTRA, COLUMN_KEY FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA  = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION ASC"
        val columns = dbConnection.nativeSelect(columnSql, dbName, it).queryMap()
        val entityCode = StringBuilder()
        var haveDate = false
        var haveDecimal = false

        entityCode.append("data class ${lineToHump(it)}(")
        columns.forEachIndexed { index, item ->
            val dataType = when (item["DATA_TYPE"].toString().toUpperCase()) {
                "VARCHAR", "CHAR", "JSON", "TEXT", "LONGTEXT", "MEDIUMTEXT", "TINYTEXT" -> "String"
                "INT", "TINYINT", "SMALLINT", "MEDIUMINT" -> "Int"
                "INTEGER", "BIGINT", "ID" -> "Long"
                "BIT" -> "Boolean"
                "FLOAT" -> "Float"
                "DOUBLE" -> "Double"
                "DECIMAL", "NUMERIC" -> {
                    haveDecimal = true
                    "BigDecimal"
                }
                "DATE", "TIME", "DATETIME", "TIMESTAMP", "YEAR" -> {
                    haveDate = true
                    "Date"
                }
                else -> "Any"
            }

            entityCode.append("var ${lineToHump(item["COLUMN_NAME"].toString(), false)}: $dataType? = null")
            if (index < columns.size - 1) {
                entityCode.append(", ")
            }
        }
        entityCode.append(") {\n")
        entityCode.append("    companion object : TableSchema(\"$it\") {\n")
        columns.forEach { item ->
            entityCode.append(
                "        val ${
                    lineToHump(
                        item["COLUMN_NAME"].toString(),
                        false
                    )
                } = column(\"${item["COLUMN_NAME"].toString()}\")"
            )
            if (item["COLUMN_KEY"].toString().toLowerCase() == "pri") {
                entityCode.append(".primaryKey()")
            }
            if (item["EXTRA"].toString().toLowerCase() == "auto_increment") {
                entityCode.append(".incr()")
            }
            entityCode.append("\n")
        }
        entityCode.append("    }\n")
        entityCode.append("}")

        val entity = StringBuilder()
        entity.append("package $packageName")
        entity.append("\n\n")
        entity.append("import dsl.TableSchema")
        if (haveDate) {
            entity.append("\nimport java.util.Date")
        }
        if (haveDecimal) {
            entity.append("\nimport java.math.BigDecimal")
        }
        entity.append("\n\n")
        entity.append(entityCode)

        File(path + lineToHump(it) + ".kt").writeText(entity.toString())
    }
}

/**
 * pgsql数据库自动生成实体类
 * @param dbConnection DBConnection 数据库查询类
 * @param dbName String 数据库名
 * @param path String 生成实体类的路径
 * @param packageName String 包名
 */
fun generateEntityForPgsql(dbConnection: DBConnection, dbName: String, path: String, packageName: String) {
    val tables = dbConnection.nativeSelect(
        "SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tableowner = ?",
        dbName
    ).queryMap()
    if (tables.isEmpty()) {
        return
    }

    val tableNames = tables.map { it["tablename"].toString() }

    tableNames.forEach {
        val columnSql =
            "SELECT column_name, udt_name, column_default FROM information_schema.\"columns\" WHERE table_catalog = ? AND table_name = ? ORDER BY ordinal_position ASC"
        val columns = dbConnection.nativeSelect(columnSql, dbName, it).queryMap()

        val pkColSql = """
            SELECT
                pg_attribute.attname AS pkname
            FROM
                pg_constraint
            INNER JOIN pg_class ON
                pg_constraint.conrelid = pg_class.oid
            INNER JOIN pg_attribute ON
                pg_attribute.attrelid = pg_class.oid
                AND pg_attribute.attnum = pg_constraint.conkey[1]
            WHERE
                pg_class.relname = ?
                AND pg_constraint.contype = 'p'
        """.trimIndent()
        val pkCols = dbConnection.nativeSelect(pkColSql, it).queryMap().map { pk -> pk["pkname"].toString() }

        val entityCode = StringBuilder()
        var haveDate = false
        var haveDecimal = false

        entityCode.append("data class ${lineToHump(it)}(")
        columns.forEachIndexed { index, item ->
            val dataType = when (item["udt_name"].toString().toUpperCase()) {
                "VARCHAR", "CHAR", "JSON", "JSONB", "TEXT" -> "String"
                "INT2", "INT4" -> "Int"
                "INT8", "BIGINT", "ID" -> "Long"
                "BIT", "BOOL" -> "Boolean"
                "FLOAT4" -> "Float"
                "FLOAT8", "MONEY" -> "Double"
                "NUMERIC" -> {
                    haveDecimal = true
                    "BigDecimal"
                }
                "TIME", "TIMESTAMP" -> {
                    haveDate = true
                    "Date"
                }
                else -> "Any"
            }

            entityCode.append("var ${lineToHump(item["column_name"].toString(), false)}: $dataType? = null")
            if (index < columns.size - 1) {
                entityCode.append(", ")
            }
        }
        entityCode.append(") {\n")
        entityCode.append("    companion object : TableSchema(\"$it\") {\n")
        columns.forEach { item ->
            entityCode.append(
                "        val ${
                    lineToHump(
                        item["column_name"].toString(),
                        false
                    )
                } = column(\"${item["column_name"].toString()}\")"
            )
            if (item["column_name"].toString() in pkCols) {
                entityCode.append(".primaryKey()")
            }
            if (item["column_default"].toString().toLowerCase().contains("nextval")) {
                entityCode.append(".incr()")
            }
            entityCode.append("\n")
        }
        entityCode.append("    }\n")
        entityCode.append("}")

        val entity = StringBuilder()
        entity.append("package $packageName")
        entity.append("\n\n")
        entity.append("import dsl.TableSchema")
        if (haveDate) {
            entity.append("\nimport java.util.Date")
        }
        if (haveDecimal) {
            entity.append("\nimport java.math.BigDecimal")
        }
        entity.append("\n\n")
        entity.append(entityCode)

        File(path + lineToHump(it) + ".kt").writeText(entity.toString())
    }
}

/**
 * 驼峰风格转下划线风格
 * @param string String 待转换字符串
 * @return String 转换后的字符串
 */
fun humpToLine(string: String): String {
    val builder = StringBuilder()
    string.forEachIndexed { index, it ->
        if (it.isUpperCase() && index > 0) {
            builder.append("_")
        }
        builder.append(it.toLowerCase())
    }

    return builder.toString()
}

/**
 * 下划线风格转驼峰风格
 * @param string String 待转换字符串
 * @return String 转换后的字符串
 */
fun lineToHump(string: String, firstUpper: Boolean = true): String {
    val builder = StringBuilder()
    var upperCase = false
    for (i in string.indices) {
        if (string[i] == '_') {
            upperCase = true
            continue
        }

        if (upperCase || i == 0 && firstUpper) {
            builder.append(string[i].toUpperCase())
            upperCase = false
        } else {
            builder.append(string[i])
        }
    }

    return builder.toString()
}